
#include <d3dx9math.h>
#include "Camera.h"
#include "Input.h"
#include "Engine.h"
#include "Entity.h"
#include "BBox.h"

//
// Constructor
//

Camera::Camera() 
{
	Reset();

	// Set speeds
	linearSpeed = 0.1f;
	rotationalSpeed = 0.0004f;
	xSensitivity = 50;
	ySensitivity = 50;

	// Initialize matrix
	projMatrix = 0;
	zoomLevel = 1.0f;
	updateFOV = true;
}

//
// UpdateCamera
//

void Camera::UpdateCamera()
{
	updateView = true;
	Geometry::updateBillboards = true;
}

//
// Set Variables
//

void Camera::SetPosition(Real3 pos)  
{ 
	position = pos; 
	UpdateCamera();
}

void Camera::SetDirection(Real3 dir) 
{ 
	direction = dir; 
	direction.Normalize();
	UpdateCamera();
}

void Camera::SetUp(Real3 _up)
{ 
	up = _up; 
	UpdateCamera();
}

void Camera::SetRight(Real3 _right)
{ 
	right = _right;
	UpdateCamera();
}

//
// SetResolution
//

void Camera::SetResolution(ushort _width, ushort _height)
{
	width = _width;
	height = _height;
}

//
// Reset
//

void Camera::Reset()
{
	position.Set(0.0f, 0.0f, -5.0f);

	direction.Set(0.0f, 0.0f, 1.0f);
	up.Set(0.0f, 1.0f, 0.0f);
	right.Set(1.0f, 0.0f, 0.0f);

	UpdateCamera();
}

//
// ViewMatrix
//

D3DXMATRIX *Camera::ViewMatrix() 
{
	static D3DXMATRIX *viewMatrix = new D3DXMATRIX();

	if(updateView)
	{
    D3DXMatrixIdentity(viewMatrix);

    // Vector regeneration
    D3DXVec3Normalize((D3DXVECTOR3*)&direction, (D3DXVECTOR3*)&direction);
    D3DXVec3Cross((D3DXVECTOR3*)&right, (D3DXVECTOR3*)&up, (D3DXVECTOR3*)&direction);
    D3DXVec3Normalize((D3DXVECTOR3*)&right, (D3DXVECTOR3*)&right);
    D3DXVec3Cross((D3DXVECTOR3*)&up, (D3DXVECTOR3*)&direction, (D3DXVECTOR3*)&right);
    D3DXVec3Normalize((D3DXVECTOR3*)&up, (D3DXVECTOR3*)&up);

    // Build the view matrix
    viewMatrix->_11 = right.x; viewMatrix->_12 = up.x; viewMatrix->_13 = direction.x;
    viewMatrix->_21 = right.y; viewMatrix->_22 = up.y; viewMatrix->_23 = direction.y;
    viewMatrix->_31 = right.z; viewMatrix->_32 = up.z; viewMatrix->_33 = direction.z;

    viewMatrix->_41 =- D3DXVec3Dot((D3DXVECTOR3*)&position, (D3DXVECTOR3*)&right);
    viewMatrix->_42 =- D3DXVec3Dot((D3DXVECTOR3*)&position, (D3DXVECTOR3*)&up);
    viewMatrix->_43 =- D3DXVec3Dot((D3DXVECTOR3*)&position, (D3DXVECTOR3*)&direction);

		/*Real3 lookAt = position + direction;
		D3DXMatrixLookAtLH(viewMatrix, (D3DXVECTOR3*)&position, (D3DXVECTOR3*)&lookAt, (D3DXVECTOR3*)&up);*/
		updateView = false;
	}

	return viewMatrix;
}

//
// ProjMatrix
//

D3DXMATRIX *Camera::ProjMatrix() 
{
	if(updateFOV)
	{
		if(!projMatrix) delete projMatrix;

		projMatrix = new D3DXMATRIX();
		D3DXMatrixPerspectiveFovLH(projMatrix, D3DX_PI/(4.0f * zoomLevel), AspectRatio(), NEAR_PLANE, FAR_PLANE);
	}

	return projMatrix;
}

//
// Change Direction by rotating
//

void Camera::RotateRight(float theta)
{
	direction.Rotate(up, theta);
	right.Rotate(up, theta);

	#ifdef NORMALIZE_ROTATIONS
	direction.Normalize();
	right.Normalize();
	#endif

	UpdateCamera();
}

void Camera::RotateUp(float theta)
{
	direction.Rotate(right, theta);
	up.Rotate(right, theta);

	#ifdef NORMALIZE_ROTATIONS
	direction.Normalize();
	up.Normalize();
	#endif

	UpdateCamera();
}

void Camera::RotateCW(float theta)
{
	up.Rotate(direction, theta);
	right.Rotate(direction, theta);

	#ifdef NORMALIZE_ROTATIONS
	up.Normalize();
	right.Normalize();
	#endif

	UpdateCamera();
}

//
// Changing Position
//

void Camera::MoveForward(float dist)
{
	position += dist * direction;
	UpdateCamera();
}

void Camera::MoveRight(float dist)
{
	position += dist * right;
	UpdateCamera();
}

void Camera::MoveUp(float dist)
{
	position += dist * up;
	UpdateCamera();
}

//
// MoveCamera
//

void Camera::MoveCamera()
{
	// Keys
	if(input.KeyPressed(DIK_W))
		MoveForward( linearSpeed);
	if(input.KeyPressed(DIK_S))
		MoveForward(-linearSpeed);

	if(input.KeyPressed(DIK_D))
		MoveRight( linearSpeed);
	if(input.KeyPressed(DIK_A))
		MoveRight(-linearSpeed);

	if(input.KeyPressed(DIK_SPACE))
	{
		BBox box = engine.scene->TreeBox();
		if(box.IsGood())
			LookAt(&box);
		else
			Reset();
	}

	if(input.MouseZ())
		MoveForward(linearSpeed * input.MouseZ() * 0.025f);
	
	// Rotate Camera
	RotateCamera();
}

//
// RotateCamera
//

void Camera::RotateCamera()
{
	//static float pitch = 0.0f;
	D3DXMATRIX mtxRot; 
	
	float x = rotationalSpeed * xSensitivity;
	float y = rotationalSpeed * ySensitivity;

	if(input.MouseY())
  {
    D3DXMatrixRotationAxis(&mtxRot, (D3DXVECTOR3*)&right, (float)input.MouseY() * y * 0.07f / zoomLevel);
    D3DXVec3TransformCoord((D3DXVECTOR3*)&direction, (D3DXVECTOR3*)&direction, &mtxRot);
    D3DXVec3TransformCoord((D3DXVECTOR3*)&up, (D3DXVECTOR3*)&up, &mtxRot);
  }

  if(input.MouseX())
  {
    D3DXMatrixRotationAxis(&mtxRot, &D3DXVECTOR3(0,1,0), (float)input.MouseX() * x * 0.07f / zoomLevel);
    D3DXVec3TransformCoord((D3DXVECTOR3*)&direction, (D3DXVECTOR3*)&direction, &mtxRot);
    D3DXVec3TransformCoord((D3DXVECTOR3*)&up, (D3DXVECTOR3*)&up, &mtxRot);
  }
	UpdateCamera();
}

//
// DrawCameraStats
//

void Camera::DrawCameraStats() 
{
	TCHAR str[50];

	StringCchPrintf(str, 50, L"Cam Pos <%3.3f,%3.3f,%3.3f>", 
		        position.x, position.y, position.z);
	engine.DrawStringSmall(5, 5, str);

	StringCchPrintf(str, 50, L"Dir <%3.3f,%3.3f,%3.3f>", 
		        direction.x, direction.y, direction.z);
	engine.DrawStringSmall(34, 17, str);
}

//
// LookAt
//

void Camera::LookAt(BBox *box)
{
	Real3 min = box->Min(); min.x = 0;
	Real3 max = box->Max(); max.x = 0;

	Real3 n = max - min;
	Real3 p = n * 0.8f + max;
	n *= -1.0f;
	n.Normalize();

	position = p;
	direction = n;
	right.Set(-1.0f, 0.0f, 0.0f);
	up = Cross(right, direction);

	//right *= -1.0f;
	up *= -1.0f;

	UpdateCamera();
}

//
// Write
//

void Camera::Write(FileStream &file)
{
	if(file.IsBinary())
	{
		position.Write(file);
		direction.Write(file);
		up.Write(file);
		right.Write(file);
	}
	else
	{
		file.oFile << "Camera\n";
		file.oFile << "{\n";
		file.oFile << "Position ";  position.Write(file);
		file.oFile << "Direction "; direction.Write(file);
		file.oFile << "Up ";        up.Write(file);
		file.oFile << "Right ";     right.Write(file);
		file.oFile << "}\n";
	}
}

//
// Read 
//

void Camera::Read(FileStream &file)
{
	if(file.IsBinary())
	{
		position.Read(file);
		direction.Read(file);
		up.Read(file);
		right.Read(file);

		updateView = true;
	}
	else
	{
		/*file << "Camera\n";
		file << "{\n";
		file << "Position ";  position.Write(file, false);
		file << "Direction "; direction.Write(file, false);
		file << "Up ";        up.Write(file, false);
		file << "Right ";     right.Write(file, false);
		file << "}\n";*/
	}
}

//
// FirstPersonMovement
//

void Camera::FirstPersonMovement()
{
	RotateCamera();
}

//
// Zoom
//

void Camera::SetZoom(float lev) 
{ 
	zoomLevel = min(MAX_ZOOM_FACTOR, max(MIN_ZOOM_FACTOR, lev)); 
	updateFOV = true; 
}

void Camera::IncreaseZoom(float lev) 
{ 
	zoomLevel = min(MAX_ZOOM_FACTOR, max(MIN_ZOOM_FACTOR, zoomLevel+(lev*zoomLevel*0.3f))); 
	updateFOV = true; 
}